package com.lagou.edu.component;

import com.lagou.edu.model.PageInfo;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;

@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class})
})
public class PagePlugin implements Interceptor {

    /*
        拦截方法：只要被拦截的目标对象的目标方法被执行时，每次都会执行intercept方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强....");
        //获取真实对象
        StatementHandler statementHandler = this.getRealTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        //判断是否SELECT操作
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()) {
            return invocation.proceed();
        }
        // 针对定义了rowBounds，做为mapper接口方法的参数
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        Object paramObj = boundSql.getParameterObject();

        // 判断参数里是否有page对象
        PageInfo<?> page = this.findPage(paramObj).orElse(null);

        /*
         * 不需要分页的场合，如果 size 小于 0 返回结果集
         */
        if (null == page || page.getSize() < 0) {
            System.out.println("非分页查询");
            return invocation.proceed();
        }

        String originalSql = boundSql.getSql();
        Connection connection = (Connection) invocation.getArgs()[0];

        if (page.isSearchCount()) {
            this.queryTotal("select count(*) from (" + originalSql + ") tmp", page, connection);
        }
        String paginationSql = this.buildPaginationSql(originalSql, page.getPage(), page.getSize());
        metaObject.setValue("delegate.boundSql.sql", paginationSql);
        //原方法执行
        return invocation.proceed();
    }

    private String buildPaginationSql(String originalSql, int page, int size) {
        return originalSql + " limit " + page + "," + size;
    }

    private StatementHandler getRealTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return getRealTarget(metaObject.getValue("h.target"));
        }
        return (StatementHandler) target;
    }

    public Optional<PageInfo<?>> findPage(Object parameterObject) {
        if (parameterObject != null) {
            if (parameterObject instanceof Map) {
                Map<?, ?> parameterMap = (Map<?, ?>) parameterObject;
                for (Map.Entry entry : parameterMap.entrySet()) {
                    if (entry.getValue() != null && entry.getValue() instanceof PageInfo) {
                        return Optional.of((PageInfo) entry.getValue());
                    }
                }
            } else if (parameterObject instanceof PageInfo) {
                return Optional.of((PageInfo) parameterObject);
            }
        }
        return Optional.empty();
    }

    public void queryTotal(String sql, PageInfo<?> page, Connection connection) {
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            long total = 0;
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    total = resultSet.getLong(1);
                }
            }
            page.setTotal((int) total);
        } catch (Exception e) {
            throw new RuntimeException("Error: Method queryTotal execution error of sql", e);
        }
    }

    /*
       主要为了把当前的拦截器生成代理存到拦截器链中
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /*
        获取配置文件的参数
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是：" + properties);
    }
}
